package com.elitedatai.mchtest.media.service.impl;

import com.dji.sdk.cloudapi.device.DeviceEnum;
import com.dji.sdk.cloudapi.media.MediaSubFileTypeEnum;
import com.dji.sdk.cloudapi.media.MediaUploadCallbackRequest;
import com.dji.sdk.common.Pagination;
import com.dji.sdk.common.PaginationData;
import com.elitedatai.mchtest.component.oss.model.OssConfiguration;
import com.elitedatai.mchtest.component.oss.service.impl.OssServiceContext;
import com.elitedatai.mchtest.manage.model.dto.DeviceDictionaryDTO;
import com.elitedatai.mchtest.manage.service.IDeviceDictionaryService;
import com.elitedatai.mchtest.media.model.MediaFileDTO;
import com.elitedatai.mchtest.media.model.MediaFileEntity;
import com.elitedatai.mchtest.media.repository.MediaFileRepository;
import com.elitedatai.mchtest.media.service.IFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

/**
 * 文件服务实现类
 * 
 * 该类实现了IFileService接口，提供媒体文件的管理功能，包括：
 * - 文件存在性检查
 * - 文件保存和上传处理
 * - 文件查询（支持分页和条件查询）
 * - 文件URL获取
 * - 数据转换（实体与DTO之间的转换）
 * 
 * 主要功能模块：
 * 1. 文件指纹验证和重复检查
 * 2. 媒体文件的持久化存储
 * 3. 工作空间和任务维度的文件查询
 * 4. OSS对象存储的URL生成
 * 5. DJI设备信息的解析和存储
 * 
 * @author 系统生成
 * @version 1.0
 * @since 2024
 */
@Service
@Transactional
public class FileServiceImpl implements IFileService {

    /**
     * 媒体文件数据访问层，用于数据库操作
     */
    @Autowired
    private MediaFileRepository repository;

    /**
     * 设备字典服务，用于获取设备信息
     */
    @Autowired
    private IDeviceDictionaryService deviceDictionaryService;

    /**
     * OSS对象存储服务上下文，用于文件存储操作
     */
    @Autowired
    private OssServiceContext ossService;

    /**
     * 根据工作空间ID和文件指纹查询媒体文件
     * 
     * 该方法用于通过文件的唯一指纹标识在指定工作空间中查找文件，
     * 主要用于文件去重和存在性验证。
     * 
     * @param workspaceId 工作空间ID，用于隔离不同工作空间的文件
     * @param fingerprint 文件指纹，文件的唯一标识符
     * @return Optional<MediaFileEntity> 包装的媒体文件实体，如果不存在则为空
     */
    private Optional<MediaFileEntity> getMediaByFingerprint(String workspaceId, String fingerprint) {
        // 通过工作空间ID和指纹查询文件实体
        MediaFileEntity fileEntity = repository.findByWorkspaceIdAndFingerprint(workspaceId, fingerprint);
        // 将结果包装为Optional以避免空指针异常
        return Optional.ofNullable(fileEntity);
    }

    /**
     * 根据工作空间ID和文件ID查询媒体文件
     * 
     * 该方法用于通过系统生成的文件ID在指定工作空间中查找文件，
     * 主要用于文件的精确定位和访问。
     * 
     * @param workspaceId 工作空间ID，用于隔离不同工作空间的文件
     * @param fileId 系统生成的文件唯一标识符
     * @return Optional<MediaFileEntity> 包装的媒体文件实体，如果不存在则为空
     */
    private Optional<MediaFileEntity> getMediaByFileId(String workspaceId, String fileId) {
        // 通过工作空间ID和文件ID查询文件实体
        MediaFileEntity fileEntity = repository.findByWorkspaceIdAndFileId(workspaceId, fileId);
        // 将结果包装为Optional以避免空指针异常
        return Optional.ofNullable(fileEntity);
    }

    /**
     * 检查指定工作空间中是否存在具有给定指纹的文件
     * 
     * 该方法用于文件上传前的重复性检查，避免重复上传相同的文件，
     * 提高存储效率并减少不必要的网络传输。
     * 
     * @param workspaceId 工作空间ID，限定查询范围
     * @param fingerprint 文件指纹，用于唯一标识文件内容
     * @return Boolean 如果文件存在返回true，否则返回false
     */
    @Override
    public Boolean checkExist(String workspaceId, String fingerprint) {
        // 调用私有方法查询文件，并检查是否存在
        return this.getMediaByFingerprint(workspaceId, fingerprint).isPresent();
    }

    /**
     * 保存媒体文件到数据库
     * 
     * 该方法处理媒体文件的上传回调请求，将文件信息转换为数据库实体并持久化存储。
     * 同时为文件生成唯一的系统ID，并关联到指定的工作空间。
     * 
     * @param workspaceId 工作空间ID，用于文件的逻辑隔离
     * @param file 媒体上传回调请求对象，包含文件的详细信息
     * @return Long 保存后的文件实体主键ID
     */
    @Override
    public Long saveFile(String workspaceId, MediaUploadCallbackRequest file) {
        // 将上传请求对象转换为数据库实体
        MediaFileEntity fileEntity = this.fileUploadConvertToEntity(file);
        // 设置工作空间ID，实现多租户隔离
        fileEntity.setWorkspaceId(workspaceId);
        // 生成唯一的文件ID
        fileEntity.setFileId(UUID.randomUUID().toString());
        // 保存到数据库并返回主键ID
        return repository.save(fileEntity).getId();
    }

    /**
     * 获取指定工作空间的所有媒体文件
     * 
     * 该方法查询指定工作空间下的所有媒体文件，并将数据库实体转换为DTO对象返回。
     * 适用于需要获取完整文件列表的场景，但不建议在大数据量情况下使用。
     * 
     * @param workspaceId 工作空间ID，限定查询范围
     * @return List<MediaFileDTO> 媒体文件DTO列表
     */
    @Override
    public List<MediaFileDTO> getAllFilesByWorkspaceId(String workspaceId) {
        // 查询指定工作空间的所有文件并转换为DTO流
        return repository.findByWorkspaceId(workspaceId)
                .stream()
                // 将每个实体转换为DTO对象
                .map(this::entityConvertToDto)
                // 收集为列表返回
                .collect(Collectors.toList());
    }

    /**
     * 分页查询指定工作空间的媒体文件
     * 
     * 该方法提供分页功能，按ID降序排列返回媒体文件列表，
     * 适用于大数据量场景下的文件浏览和管理。
     * 
     * @param workspaceId 工作空间ID，限定查询范围
     * @param page 页码，从0开始
     * @param pageSize 每页大小，控制返回的记录数量
     * @return PaginationData<MediaFileDTO> 包含分页信息和数据的分页对象
     */
    @Override
    public PaginationData<MediaFileDTO> getMediaFilesPaginationByWorkspaceId(String workspaceId, long page, long pageSize) {
        // 创建分页请求对象，按ID降序排列（最新的文件在前）
        Pageable pageable = PageRequest.of((int)page-1, (int) pageSize, Sort.by(Sort.Direction.DESC, "id"));
        // 执行分页查询
        Page<MediaFileEntity> pageData = repository.findByWorkspaceIdOrderByDesc(workspaceId, pageable);
        // 将查询结果转换为DTO列表
        List<MediaFileDTO> records = pageData.getContent()
                .stream()
                // 实体转DTO
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());

        return new PaginationData<MediaFileDTO>(records, new Pagination(pageData.getNumber() + 1, pageData.getSize(), pageData.getTotalElements()));
    }

    /**
     * 获取媒体文件的访问URL
     * 
     * 该方法根据工作空间ID和文件ID查找文件，然后通过OSS服务生成可访问的URL。
     * 主要用于文件的下载和预览功能。
     * 
     * @param workspaceId 工作空间ID，用于文件定位
     * @param fileId 文件ID，系统生成的唯一标识
     * @return URL 文件的访问URL对象
     * @throws IllegalArgumentException 当文件不存在时抛出异常
     */
    @Override
    public URL getObjectUrl(String workspaceId, String fileId) {
        // 根据工作空间ID和文件ID查询文件
        Optional<MediaFileEntity> mediaFileOpt = getMediaByFileId(workspaceId, fileId);
        // 检查文件是否存在
        if (mediaFileOpt.isEmpty()) {
            throw new IllegalArgumentException("{} doesn't exist.");
        }

        // 通过OSS服务生成文件访问URL
        return ossService.getObjectUrl(OssConfiguration.bucket, mediaFileOpt.get().getObjectKey());
    }

    /**
     * 根据工作空间ID和任务ID查询媒体文件
     * 
     * 该方法用于查询特定任务产生的所有媒体文件，
     * 适用于任务管理和文件归档场景。
     * 
     * @param workspaceId 工作空间ID，限定查询范围
     * @param jobId 任务ID，用于关联特定任务的文件
     * @return List<MediaFileDTO> 属于指定任务的媒体文件DTO列表
     */
    @Override
    public List<MediaFileDTO> getFilesByWorkspaceAndJobId(String workspaceId, String jobId) {
        // 查询指定工作空间和任务的所有文件实体
        List<MediaFileEntity> entities = repository.findByWorkspaceIdAndJobId(workspaceId, jobId);
        // 将实体列表转换为DTO列表并返回
        return entities.stream()
                .map(this::entityConvertToDto)
                .collect(Collectors.toList());
    }

    /**
     * 将文件上传回调请求对象转换为数据库实体对象
     * 
     * 该方法是数据转换的核心，负责将DJI SDK的回调请求对象转换为系统内部的实体对象。
     * 包含了复杂的设备信息解析和载荷设备名称的查询逻辑。
     * 
     * @param file 媒体上传回调请求对象，包含文件的完整信息
     * @return MediaFileEntity 转换后的数据库实体对象
     */
    private MediaFileEntity fileUploadConvertToEntity(MediaUploadCallbackRequest file) {
        // 使用建造者模式创建实体对象
        MediaFileEntity.MediaFileEntityBuilder builder = MediaFileEntity.builder();

        // 检查输入参数是否为空
        if (file != null) {
            // 设置基本文件信息
            builder.fileName(file.getName())                    // 文件名
                    .filePath(file.getPath())                   // 文件路径
                    .fingerprint(file.getFingerprint())         // 文件指纹
                    .objectKey(file.getObjectKey())             // OSS对象键
                    // 安全地获取子文件类型，避免空指针异常
                    .subFileType(Optional.ofNullable(file.getSubFileType()).map(MediaSubFileTypeEnum::getType).orElse(null))
                    .isOriginal(file.getExt().getOriginal())    // 是否为原始文件
                    .jobId(file.getExt().getFileGroupId())      // 任务组ID
                    .drone(file.getExt().getSn())               // 无人机序列号
                    .tinnyFingerprint(file.getExt().getTinnyFingerprint())  // 缩略图指纹
                    .payload(file.getExt().getPayloadModelKey().getDevice()); // 载荷设备

            // 解析设备信息：domain-type-subType 三级结构
            DeviceEnum payloadModelKey = file.getExt().getPayloadModelKey();
            // 通过设备字典服务查询设备的详细信息
            Optional<DeviceDictionaryDTO> payloadDict = deviceDictionaryService
                    .getOneDictionaryInfoByTypeSubType(payloadModelKey.getDomain().getDomain(),
                            payloadModelKey.getType().getType(), payloadModelKey.getSubType().getSubType());
            // 如果找到设备信息，则使用设备名称替换默认的设备标识
            payloadDict.ifPresent(payload -> builder.payload(payload.getDeviceName()));
        }
        // 构建并返回实体对象
        return builder.build();
    }

    /**
     * 将数据库实体对象转换为文件数据传输对象
     * 
     * 该方法负责将内部的实体对象转换为对外提供的DTO对象，
     * 包含时间格式转换和数据脱敏等处理。
     * 
     * @param entity 数据库实体对象
     * @return MediaFileDTO 转换后的数据传输对象
     */
    private MediaFileDTO entityConvertToDto(MediaFileEntity entity) {
        // 使用建造者模式创建DTO对象
        MediaFileDTO.MediaFileDTOBuilder builder = MediaFileDTO.builder();

        // 检查输入参数是否为空
        if (entity != null) {
            // 设置DTO的各个字段
            builder.fileName(entity.getFileName())              // 文件名
                    .fileId(entity.getFileId())                 // 文件ID
                    .filePath(entity.getFilePath())             // 文件路径
                    .isOriginal(entity.getIsOriginal())         // 是否原始文件
                    .fingerprint(entity.getFingerprint())       // 文件指纹
                    .objectKey(entity.getObjectKey())           // OSS对象键
                    .tinnyFingerprint(entity.getTinnyFingerprint()) // 缩略图指纹
                    .payload(entity.getPayload())               // 载荷设备信息
                    // 将时间戳转换为LocalDateTime对象，使用系统默认时区
                    .createTime(LocalDateTime.ofInstant(
                            Instant.ofEpochMilli(entity.getCreateTime()), ZoneId.systemDefault()))
                    .drone(entity.getDrone())                   // 无人机信息
                    .jobId(entity.getJobId());                  // 任务ID
        }

        // 构建并返回DTO对象
        return builder.build();
    }

}
